#include <Comm/Include/AdmComm.h>
#include <string>

// ComLib
#include <Comm/Include/TCPSocket.h>

AdmComm* AdmComm::instance = 0;

AdmComm::AdmComm(void)
{
    WSADATA WsaData;
    int r = WSAStartup(MAKEWORD(2, 2), &WsaData);
    if (WSANOTINITIALISED == r)
    {
        return;
    }
    hostent* thisHost;
    thisHost = gethostbyname("");
    char* ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);
    this->local_ip = ip;
}

AdmComm::~AdmComm(void)
{
}

void AdmComm::Initialize()
{
    if (0 != instance)
    {
        throw ADMCOMM_ALERADY_INITIALIZED;
    }
    instance = new AdmComm();
}

AdmComm* AdmComm::getInstance()
{
    if (0 == instance)
    {
        throw ADMCOMM_NOT_INITIALIZED;
    }
    return instance;
}

void AdmComm::Release()
{
    if (0 == instance)
    {
        throw ADMCOMM_NOT_INITIALIZED;
    }
    delete instance;
    instance = 0;
}
std::string AdmComm::getLocalIP()
{
    return this->local_ip;
}

void AdmComm::openTCPPassiveConnection(unsigned short port, TCallbackChannelConected f, void* user_data)
{
    // creo un socket TCP al poner SOCK_STREAM e IPPROTO_TCP
    // SOCKET s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // OVERLAPPED es por default aca

    if (INVALID_SOCKET == s) 
    {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        throw -1;
    }

    SOCKADDR_IN sockAddr;
    sockAddr.sin_family =  AF_INET;
    sockAddr.sin_port = htons(port);
    
    // aca escucha en la interfaz con la ip determinada
    // con INADDR_ANY escucha en todas las interfaces

    sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    // Bind the socket.
    if (SOCKET_ERROR == bind( s, (SOCKADDR*) &sockAddr, sizeof(sockAddr))) 
    {
        int error = WSAGetLastError();
        printf("bind() port:%i failed. Error: %i\n", port, error);
        closesocket(s);
        if (WSAEADDRINUSE == error)
        {
            throw BIND_FAILED;
        }
        throw PASSIVE_OPEN_FAILED;
    }
    
    Semaphore* sem = new Semaphore();
    Tcp_Server_Function_Data* data_to_thread = new Tcp_Server_Function_Data[1];
    data_to_thread->func = f;
    data_to_thread->user_data = user_data;
    data_to_thread->port_number = port;
    data_to_thread->server_sock = s;
    data_to_thread->sem_exit = sem;
    
    CWinThread* thread = AfxBeginThread(TCPServerSocketManager, (void*)data_to_thread);
    this->server_socks_maps.insert(std::make_pair<unsigned short, SOCKET>(port, s));
    this->server_sem_maps.insert(std::make_pair<unsigned short, Semaphore*>(port, sem));
}

void AdmComm::closeTCPPassiveConnection(unsigned short port)
{
    std::map<unsigned short, SOCKET>::iterator it = this->server_socks_maps.find(port);
    if (it != this->server_socks_maps.end())
    {
        SOCKET s = it->second;
        closesocket(s);
        this->server_socks_maps.erase(it);
    }

    std::map<unsigned short, Semaphore*>::iterator it_sem = this->server_sem_maps.find(port);
    if (it_sem != this->server_sem_maps.end())
    {
        it_sem->second->wait();
        delete it_sem->second;
        this->server_sem_maps.erase(it_sem);
    }
}

Channel* AdmComm::TCPconnect(const char* ip_address, unsigned short port)
{
    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // OVERLAPPED es por default aca

    if (INVALID_SOCKET == s) 
    {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        throw CONNECTION_FAILED;
        return NULL;
    }

    SOCKADDR_IN sockAddr;
    sockAddr.sin_family =  AF_INET;
    sockAddr.sin_port = htons(port);
    sockAddr.sin_addr.s_addr = inet_addr(ip_address);
    
    if (SOCKET_ERROR == connect(s, (SOCKADDR*) &sockAddr, sizeof(sockAddr)))
    {
        printf("Failed to connect.\n");
        throw CONNECTION_FAILED;
    }

    SyncQueue* tx_queue = new SyncQueue();
    SyncQueue* rx_queue = new SyncQueue();
    Semaphore* sem_sync_canal_tx_rx_tasks = new Semaphore();

    TCPSocket* tcp_socket = new TCPSocket(s, tx_queue, rx_queue, sem_sync_canal_tx_rx_tasks);

    Channel* canal = new Channel(tx_queue, rx_queue, tcp_socket, sem_sync_canal_tx_rx_tasks);

    return canal;
}

unsigned int AdmComm::TCPServerSocketManager(void *params)
{
    Tcp_Server_Function_Data* data = (Tcp_Server_Function_Data*) params;
    if (0 != listen(data->server_sock, SOMAXCONN))
    {
        throw -1;
    }
    bool fin = false;
    while (!fin)
    {
        SOCKADDR_IN other_info;
        int size_of_other_info = sizeof(other_info);
        
        SOCKET complete_socket = accept(data->server_sock, (SOCKADDR*) &other_info, (LPINT)&size_of_other_info);
        
        if (INVALID_SOCKET == complete_socket)
        {
            int error = WSAGetLastError();
            switch (error)
            {
                case WSAEINTR:
                {
                    // Interrupted function call. A blocking operation was interrupted
                }
                default:
                {
                    fin = true;
                }
            }
        }
        else
        {
            SyncQueue* tx_queue = new SyncQueue();
            SyncQueue* rx_queue = new SyncQueue();
            Semaphore* sem_sync_canal_tx_rx_tasks = new Semaphore();

            TCPSocket* tcp_socket = new TCPSocket(complete_socket , tx_queue, rx_queue, sem_sync_canal_tx_rx_tasks);

            Channel* canal = new Channel(tx_queue, rx_queue, tcp_socket, sem_sync_canal_tx_rx_tasks);

            // llamo a la funcion callback con el canal armado para el q lo pidio
            data->func(canal, inet_ntoa(other_info.sin_addr), data->user_data);
        }
    }

    data->sem_exit->signal();
    delete[] data;
    
    return 0;
}

void AdmComm::closeChannel(Channel* canal)
{
    delete canal;
}
